﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using BookSourceVerification.service;
using Microsoft.Extensions.DependencyInjection;
using NLog;

namespace BookSourceVerification.execute;

public sealed class VerifyBookSourceExecute
{
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    public delegate void DisplayHandler(int success, int check, int sum, long time);

    public event DisplayHandler? ShowCountEvent;

    public ICheckBookSourceService CheckBookSourceService;
    public UrlCheckService UrlCheckService;

    public VerifyBookSourceExecute(ICheckBookSourceService checkBookSourceService)
    {
        CheckBookSourceService = checkBookSourceService;
        UrlCheckService = UrlCheckService.GetUrlCheckService(checkBookSourceService);
    }

    public async Task VerifyBookSourceEx(string? path)
    {
        path = path ?? throw new ArgumentNullException($"{nameof(path)} is null");
        await using var streamWriter = File.CreateText(GetPath(path));
        var jsonArrayEx = await LoadJsonEx(path);
        var array = jsonArrayEx.AsArray();
        var resultArray = new JsonArray();
        int check = 0;

        var stopwatch = Stopwatch.StartNew();
        var parallelOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = 200
        };
        await Parallel.ForEachAsync(array, parallelOptions, async (node, token) =>
        {
            try
            {
                node = node ?? throw new WarningException("json格式不对");
                var url = UrlCheckService.GetUrl(node);
                if (await CheckBookSourceService.Check(url))
                {
                    resultArray.Add(JsonNode.Parse(node.ToJsonString()));
                }
            }
            catch (Exception exception)
            {
                Logger.Error(exception);
            }
            finally
            {
                Interlocked.Increment(ref check);
                if (check % 30 == 0)
                {
                    ShowCountEvent?.Invoke(resultArray.Count, check, array.Count, stopwatch.ElapsedMilliseconds);
                }
            }
        });
        stopwatch.Stop();

        var jsonWriterOptions = new JsonWriterOptions
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };
        await using var utf8JsonWriter = new Utf8JsonWriter(streamWriter.BaseStream, jsonWriterOptions);
        resultArray.WriteTo(utf8JsonWriter);
        ShowCountEvent?.Invoke(resultArray.Count, check, array.Count, stopwatch.ElapsedMilliseconds);
    }

    private static async Task<JsonArray> LoadJsonEx(string path)
    {
        await using var stream = File.OpenRead(path);
        var jsonNode = JsonNode.Parse(stream) ?? throw new WarningException("书源json格式错误");
        return jsonNode as JsonArray ?? throw new WarningException("书源json格式错误，书源必须是数组");
    }

    /// <summary>
    /// 验证书源
    /// </summary>
    /// <param name="path">书源文件路径</param>
    /// <exception cref="ArgumentNullException"></exception>
    public async Task<(int count, int successCount, long ElapsedMilliseconds)> VerifyBookSource(string? path)
    {
        path = path ?? throw new ArgumentNullException($"{nameof(path)} is null");
        int count = 0;
        int successCount = 0;
        // 文件保存的路径
        await using var streamWriter = File.CreateText(GetPath(path));
        await using var jsonWriter = new Utf8JsonWriter(streamWriter.BaseStream, new JsonWriterOptions
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        });
        // 读取json书源内容
        var jsonDocument = await LoadJson(path);
        var parallelOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = 100
        };
        var elements = new ConcurrentBag<JsonElement>();
        var stopwatch = Stopwatch.StartNew();
        jsonWriter.WriteStartArray();
        await Parallel.ForEachAsync(jsonDocument.RootElement.EnumerateArray(), parallelOptions,
            async (element, _) =>
            {
                string url = null!;
                try
                {
                    url = UrlCheckService.GetUrl(element);

                    if (await CheckBookSourceService.Check(url))
                    {
                        // elements.Add(element);
                        lock (Logger)
                        {
                            element.WriteTo(jsonWriter);
                            Interlocked.Increment(ref successCount);
                        }
                    }
                }
                catch (Exception exception)
                {
                    Logger.Error($"【容许错误】《{url}》地址的书源验证错误信息，错误信息：{exception}");
                }
                finally
                {
                    Interlocked.Increment(ref count);
                    if (count % 50 == 0)
                    {
                        ShowCountEvent?.Invoke(successCount, count, 0, stopwatch.ElapsedMilliseconds);
                    }
                }
            });
        // jsonWriter.WriteStartArray();
        // foreach (var jsonElement in elements)
        // {
        // jsonElement.WriteTo(jsonWriter);
        // }
        jsonWriter.WriteEndArray();
        await jsonWriter.FlushAsync();
        stopwatch.Stop();
        return (count, successCount, stopwatch.ElapsedMilliseconds);
    }

    public static async Task<JsonDocument> LoadJson(string path)
    {
        await using var stream = File.OpenRead(path);
        var document = await JsonDocument.ParseAsync(stream);
        var rootElement = document.RootElement;
        if (rootElement.ValueKind != JsonValueKind.Array)
        {
            throw new WarningException("书源json格式错误，书源必须是数组");
        }

        return document;
    }

    private string GetPath(string? path)
    {
        var directoryName = Path.GetDirectoryName(path)
                            ?? AppDomain.CurrentDomain.BaseDirectory;
        return directoryName + @"\success.json";
    }
}